package com.only4play.web.resource;

import com.nimbusds.jose.jwk.RSAKey;
import com.only4play.captcha.CaptchaConfig;
import com.only4play.captcha.CaptchaPayload;
import com.only4play.captcha.CaptchaService;
import com.only4play.common.constants.ErrorCodeEnum;
import com.only4play.common.exception.ApplicationException;
import com.only4play.common.model.GeneralResponse;
import com.only4play.common.utils.CommonUtils;
import com.only4play.common.utils.JwksUtils;
import com.only4play.common.utils.MD5Utils;
import com.only4play.dto.PersonLoginDto;
import com.only4play.dto.PersonRegisterDto;
import com.only4play.jwt.JwtCodec;
import com.only4play.jwt.JwtConfig;
import com.only4play.jwt.JwtPayload;
import com.only4play.service.UserPersonalAccountService;
import com.only4play.vo.UserPersonAccountVo;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author liyuncong
 * @version 1.0
 * @file AccountLoginResource
 * @brief 账号登录注册资源
 * @details 账号登录注册资源
 * @date 2024-02-20
 *
 * Edit History
 * ----------------------------------------------------------------------------
 * DATE                     NAME               DESCRIPTION
 * 2024-02-20               liyuncong          Created
 */

@Slf4j
@RestController
@RequestMapping(
    value = "/api/v1/account",
    produces = MediaType.APPLICATION_JSON_VALUE
)
public class AccountLoginResource {


    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private CaptchaService captchaService;
    @Autowired
    private CaptchaConfig captchaConfig;
    @Autowired
    private JwtCodec<?> jwtCodec;
    @Autowired
    private JwtConfig jwtConfig;

    @Autowired
    private UserPersonalAccountService userPersonalAccountService;

    /**
     * 用户注册
     *
     * @param registerDto 用户注册数据
     * @return 注册结果
     */
    @PostMapping("/register")
    public GeneralResponse<Boolean> register(
        @Valid @NotNull @RequestBody PersonRegisterDto registerDto
    ) {
        return GeneralResponse.of(userPersonalAccountService.register(registerDto));
    }

    /**
     * 通过图片验证码登录
     *
     * @param loginDto 登录模型
     * @return 登录信息
     */
    @PostMapping(value = "/loginByCaptcha")
    public GeneralResponse<?> login(
        @Valid @NotNull @RequestBody PersonLoginDto loginDto
    ) {
        Map<String, Object> result = new HashMap<>();
        // 参数校验
        if (!loginDto.validate()) {
            throw new ApplicationException(ErrorCodeEnum.VALIDATE_ERROR);
        }

        // 校验验证码
        String captchaKey = loginDto.getCaptchaKey();
        String captcha = loginDto.getCaptcha();
        // 从redis获取验证码
        String redisKey = MD5Utils.encrypt(captchaKey.toLowerCase() + captcha.toLowerCase());
        CaptchaPayload captchaPayload = (CaptchaPayload) redisTemplate.boundValueOps(redisKey).get();
        if (Objects.isNull(captchaPayload)) {
            throw new ApplicationException(ErrorCodeEnum.VALIDATE_CAPTCHA_TIMEOUT);
        }
        if (captchaPayload.getConfig().getIgnoreCase()) {
            // 忽略验证码大小写
            if (!captchaKey.equalsIgnoreCase(captchaPayload.getRandomKey())
                && captcha.equalsIgnoreCase(captchaPayload.getCode())) {
                throw new ApplicationException(ErrorCodeEnum.VALIDATE_CAPTCHA_ERROR);
            }
        } else {
            // 校验验证码大小写
            if (!captchaKey.equals(captchaPayload.getRandomKey())
                && captcha.equals(captchaPayload.getCode())) {
                throw new ApplicationException(ErrorCodeEnum.VALIDATE_CAPTCHA_ERROR);
            }
        }

        // 进行私钥解密
        String rasKey = loginDto.getRasKey();
        String encryptPassword = loginDto.getLoginPassword();
        String decryptPassword = "";
        String privateKeyStr = (String) redisTemplate.boundValueOps(rasKey).get();
        if (!StringUtils.hasText(privateKeyStr)) {
            throw new ApplicationException(ErrorCodeEnum.VALIDATE_CAPTCHA_TIMEOUT);
        }
        // 获取私钥
        try {
            RSAPrivateKey privateKey = (RSAPrivateKey) JwksUtils.privateKeyFromString(privateKeyStr);
            decryptPassword = JwksUtils.decrypt(privateKey, encryptPassword);
        } catch (Exception exception) {
            throw new ApplicationException(ErrorCodeEnum.INVALID_USERNAME_OR_PASSWORD, exception);
        }
        // 删除缓存私钥
//        redisTemplate.delete(rasKey);

        // 验证用户
        String loginUsername = loginDto.getLoginUsername();
        Boolean authenticate = userPersonalAccountService.authenticate(loginUsername, decryptPassword);
        if (!authenticate) {
            throw new ApplicationException(ErrorCodeEnum.INVALID_USERNAME_OR_PASSWORD);
        }

        // 获取登录用户
        UserPersonAccountVo loginUser = userPersonalAccountService.queryByLoginUsername(loginUsername);

        // 校验用户状态

        // 组装登录信息
        // 生成TOKEN
        JwtPayload payload = new JwtPayload<>(
            "anonymous", loginUser.getLoginUsername(), loginUser.getPersonVo());
        String token = jwtCodec.encode(payload, jwtConfig);
        // 设置token缓存有效时间
        String PREFIX_USER_TOKEN = "prefix_user_token_";
        redisTemplate.boundValueOps(PREFIX_USER_TOKEN + token).set(payload, jwtConfig.getExpireSeconds(), TimeUnit.SECONDS);

        // 记录登录日志

        // 返回登录结果
        result.put("userInfo", loginUser);
        result.put("token", token);
        return GeneralResponse.of(result);
    }

    /**
     * 获取图片验证码
     *
     * @param key 随机KEY
     * @return 图片验证码base64字符串
     */
    @GetMapping(value = "/captcha/image/{key}")
    public GeneralResponse<String> imageCaptcha(
        @PathVariable("key") String key
    ) {
        CaptchaPayload payload = captchaService.generate(captchaConfig, key);
        String base64 = payload.base64();

        // 生成redisKey，将验证码存储到redis
        String redisKey = MD5Utils.encrypt(key.toLowerCase() + payload.getCode().toLowerCase());
        redisTemplate.opsForValue().set(redisKey, payload, payload.getExpireTime(), TimeUnit.SECONDS);
        log.info("image captcha, key : {}, code : {}", key, payload.getCode());

        return GeneralResponse.of(base64);
    }

    /**
     * 获取密码加密的公钥
     *
     * @return 公钥
     */
    @GetMapping(value = "/getPublicKeyRas")
    public GeneralResponse<Map<String, String>> getPublicKeyRas() {
        try {
            // 生成缓存key
            String key = CommonUtils.uuid();

            // 生成RAS密钥对
            RSAKey rsaKey = JwksUtils.generateRsa();

            RSAPublicKey publicKey = rsaKey.toRSAPublicKey();
            RSAPrivateKey privateKey = rsaKey.toRSAPrivateKey();

            // TODO DELETE
            String encrypt = JwksUtils.encrypt(publicKey, "!d@+2R4Y8#H");
            log.info("password :{}", encrypt);

            // 将密钥经Base64转称string
            String publicKeyToString = JwksUtils.publicKeyToString(publicKey);
            String privateKeyToString = JwksUtils.privateKeyToString(privateKey);

            log.info("publicKey: {}", publicKeyToString);
            log.info("privateKey: {}", privateKeyToString);

            // 存入redis，过期时间15分钟
            redisTemplate.opsForValue().set(key, privateKeyToString, 15, TimeUnit.MINUTES);

            Map<String, String> result = new HashMap<>();
            result.put("key", key);
            result.put("publicKey", publicKeyToString);

            return GeneralResponse.of(result);
        } catch (Exception exception) {
            throw new ApplicationException(ErrorCodeEnum.SERVER_ERROR, exception);
        }
    }

}
